/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <agile.h> 
#include <baja/common.hpp>
#include <baja/app/app.hpp>
#include <baja/app/app_shell.hpp>
#include <baja/observable.hpp>

using namespace Platform;
using namespace Windows::System;
using namespace Windows::UI::Core;
using namespace Windows::ApplicationModel;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::ApplicationModel::Activation;
using namespace Windows::Foundation;
using namespace Windows::Devices::Input;
using namespace Windows::UI::Input;
using namespace Windows::Graphics::Display;
using namespace Windows::UI::Notifications;
using namespace Windows::Data::Xml::Dom;

namespace baja { namespace app { namespace directui {

struct suspension_deferral : public isuspension_deferral
{
    suspension_deferral(SuspendingDeferral^ parent) : _parent(parent) {}

    void complete() { _parent->Complete(); }

    SuspendingDeferral^ _parent;
};

class shell : public iloopless_app_shell, public iapp_shell_service, public std::enable_shared_from_this<shell>
{
public:
    BAJA_DECLARE_TAG("9ED4A516-F856-40F6-80EA-8E152B454382");

private:
    // inner_directui_shell has to be a 'ref class' because it is easier to handle events fired from
    // the metro app or metro window this way (see initialize).
    ref class inner_directui_shell
    {
    private:
        std::weak_ptr<shell> _shell;
        std::weak_ptr<iapp> _app;
        Platform::Agile<Windows::UI::Input::GestureRecognizer> _gestureRecognizer;
        bool _emulatingTouch;
        m::point2d _windowSize;
        std::map<uint32, pointer_args> _activeTouchPoints; 

        Platform::Agile<Windows::UI::Xaml::Application> _shellApp;
        Platform::Agile<Windows::UI::Core::CoreWindow> _shellWindow;
        Windows::UI::Xaml::UIElement^ _topLevelDirectUIElement;
        TileUpdater^ _tileUpdater;

    internal:
        inner_directui_shell(
            const std::shared_ptr<shell>& shell, 
            const std::shared_ptr<iapp>& app, 
            Windows::UI::Xaml::UIElement^ topLevelDirectUIElement
            ) :
            _shell(shell),
            _app(app), 
            _topLevelDirectUIElement(topLevelDirectUIElement),
            _emulatingTouch(false)
        {}

        void initialize(
            Windows::UI::Xaml::Application^ app, 
            Windows::UI::Core::CoreWindow^ window
            )
        {
            app->UnhandledException += ref new Windows::UI::Xaml::UnhandledExceptionEventHandler(
                this,
                &inner_directui_shell::onUnhandledException
                );

            app->Resuming += ref new Windows::Foundation::EventHandler<Platform::Object^>(
                this, 
                &inner_directui_shell::onResuming
                );

            app->Suspending += ref new Windows::UI::Xaml::SuspendingEventHandler(
                this, 
                &inner_directui_shell::onSuspending
                );

            window->Closed += ref new TypedEventHandler<Windows::UI::Core::CoreWindow^, CoreWindowEventArgs^>(
                this, 
                &inner_directui_shell::onClosed
                );

            window->SizeChanged += ref new TypedEventHandler<Windows::UI::Core::CoreWindow^, WindowSizeChangedEventArgs^>(
                this, 
                &inner_directui_shell::onWindowSizeChanged);

            window->VisibilityChanged += ref new TypedEventHandler<Windows::UI::Core::CoreWindow^, Windows::UI::Core::VisibilityChangedEventArgs^>(
                this,
                &inner_directui_shell::onVisibilityChanged);

            // input events should be handled off the top-level DirectUI element since
            // we assume that all DirectUI elements are on top of all application elements.

            // pointer handlers
            _topLevelDirectUIElement->PointerMoved += ref new Windows::UI::Xaml::Input::PointerEventHandler(
                this,
                &inner_directui_shell::onPointerMoved
                );
            _topLevelDirectUIElement->PointerPressed += ref new Windows::UI::Xaml::Input::PointerEventHandler(
                this,
                &inner_directui_shell::onPointerPressed
                );
            _topLevelDirectUIElement->PointerWheelChanged += ref new Windows::UI::Xaml::Input::PointerEventHandler(
                this,
                &inner_directui_shell::onPointerWheelChanged
                );
            _topLevelDirectUIElement->PointerReleased += ref new Windows::UI::Xaml::Input::PointerEventHandler(
                this,
                &inner_directui_shell::onPointerReleased
                );

            // key handlers
            _topLevelDirectUIElement->KeyDown += ref new Windows::UI::Xaml::Input::KeyEventHandler(
                this, 
                &inner_directui_shell::onKeyDown
                );

            DisplayProperties::OrientationChanged += ref new DisplayPropertiesEventHandler(this, &inner_directui_shell::onOrientationChanged);
            DisplayProperties::LogicalDpiChanged += ref new DisplayPropertiesEventHandler(this, &inner_directui_shell::onLogicalDpiChanged);

            // gestures
            _gestureRecognizer = ref new GestureRecognizer();

            _gestureRecognizer->ShowGestureFeedback = false;
            _gestureRecognizer->GestureSettings = 
                GestureSettings::Hold |
                GestureSettings::HoldWithMouse |
                GestureSettings::Tap |
                GestureSettings::DoubleTap |
                GestureSettings::ManipulationRotate | 
                GestureSettings::ManipulationScale | 
                GestureSettings::ManipulationTranslateX | 
                GestureSettings::ManipulationTranslateY |
                GestureSettings::ManipulationTranslateInertia;

            _gestureRecognizer->ManipulationStarted += ref new TypedEventHandler<GestureRecognizer^, ManipulationStartedEventArgs^>(
                this,
                &inner_directui_shell::onManipulationStarted
                );

            _gestureRecognizer->ManipulationUpdated += ref new TypedEventHandler<GestureRecognizer^, ManipulationUpdatedEventArgs^>(
                this,
                &inner_directui_shell::onManipulationUpdated
                );

            _gestureRecognizer->ManipulationCompleted += ref new TypedEventHandler<GestureRecognizer^, ManipulationCompletedEventArgs^>(
                this,
                &inner_directui_shell::onManipulationCompleted
                );

            _gestureRecognizer->Holding += ref new TypedEventHandler<GestureRecognizer^, HoldingEventArgs^>(
                this,
                &inner_directui_shell::onHolding
                );

            _gestureRecognizer->Tapped += ref new TypedEventHandler<GestureRecognizer^, TappedEventArgs^>(
                this,
                &inner_directui_shell::onTapped
                );

            // initialize window size
            _windowSize.x = static_cast<float32>(window->Bounds.Width);
            _windowSize.y = static_cast<float32>(window->Bounds.Height);

            _tileUpdater = TileUpdateManager::CreateTileUpdaterForApplication();
            _tileUpdater->EnableNotificationQueue(true);

            _app.lock()->initialize();
        }

        baja::uint32 touchPointCount() { return (uint32)_activeTouchPoints.size(); }

        const std::map<uint32, pointer_args>& touchPoints() { return _activeTouchPoints; }

        baja::app::orientation orientation() { return toOrientation(DisplayProperties::CurrentOrientation); }

        void enableInertia(const float32 deceleration)
        {
            _gestureRecognizer->AutoProcessInertia = true;
            _gestureRecognizer->InertiaTranslationDeceleration = deceleration;
        }

        void disableInertia()
        {
            _gestureRecognizer->AutoProcessInertia = false;
        }

        bool inertiaEnabled() { return _gestureRecognizer->AutoProcessInertia; }

        float32 logicalDpi() 
        {
            // TRACK 31: dpi not working right!
            ResolutionScale rs = DisplayProperties::ResolutionScale;

            return DisplayProperties::LogicalDpi;
        }

        const m::point2d& windowSize()
        {
            return _windowSize;
        }

        void clearTile()
        {
            _tileUpdater->Clear();
        }

        void updateTile(const std::wstring& fileName)
        {
            // generate file path
            std::wostringstream outs;
            outs << L"ms-appdata:///local/";
            outs << fileName;
            std::wstring path = outs.str();

            // wide tile
            auto temp = Windows::UI::Notifications::TileTemplateType::TileWideImage;
            XmlDocument^ wideXml = Windows::UI::Notifications::TileUpdateManager::GetTemplateContent(temp);
            XmlNodeList^ list = wideXml->GetElementsByTagName("image");
            XmlElement^ e = (XmlElement^)(list->Item(0));
            e->SetAttribute(L"src", ref new String(path.c_str())); 

            // square tile
            temp = Windows::UI::Notifications::TileTemplateType::TileSquareImage;
            XmlDocument^ squareXml = Windows::UI::Notifications::TileUpdateManager::GetTemplateContent(temp);
            list = squareXml->GetElementsByTagName("image");
            e = (XmlElement^)(list->Item(0));
            e->SetAttribute(L"src", ref new String(path.c_str())); 

            auto node = wideXml->ImportNode(squareXml->GetElementsByTagName("binding")->Item(0), true);
            wideXml->GetElementsByTagName("visual")->Item(0)->AppendChild(node);

            TileNotification^ notification = ref new TileNotification(wideXml);


            DEBUG_CONSOLE_TRACE(L"updating live tile");

            _tileUpdater->Clear();
            _tileUpdater->Update(notification);
        }

     private:
        
        void onUnhandledException(Platform::Object^ sender, Windows::UI::Xaml::UnhandledExceptionEventArgs^ args)
        {
            BAJA_TRACE(boost::format("!!unhandled exception!! : %s") % wstrtostr(args->Message->Data()));
        }

        void onOrientationChanged(Platform::Object^ sender)
        {
            screen_change_args a(screen_change_event_type::orientation);
            a.logicalDpi = DisplayProperties::LogicalDpi;
            a.orientation = toOrientation(DisplayProperties::CurrentOrientation);
            _app.lock()->onScreenChange(a);
        }

        void onLogicalDpiChanged(Platform::Object^ sender)
        {
            screen_change_args a(screen_change_event_type::logicalDpi);
            a.logicalDpi = DisplayProperties::LogicalDpi;
            a.orientation = toOrientation(DisplayProperties::CurrentOrientation);
            _app.lock()->onScreenChange(a);
        }

        void onClosed(Windows::UI::Core::CoreWindow^ sender, CoreWindowEventArgs^ args)
        {
            // TRACK: 184
        }

        void onWindowSizeChanged(CoreWindow^ sender, WindowSizeChangedEventArgs^ args)
        {
            resize_args a;
            a.width = static_cast<int32>(args->Size.Width);
            a.height = static_cast<int32>(args->Size.Height);

            _windowSize.x = static_cast<float32>(a.width);
            _windowSize.y = static_cast<float32>(a.height);

            _app.lock()->onResize(a);
        }

        void onVisibilityChanged(CoreWindow^ sender, VisibilityChangedEventArgs^ args)
        {
            // make sure to complete any gesture if visibility changes
            _gestureRecognizer->CompleteGesture();

            _app.lock()->onVisibilityChanged(args->Visible);
        }

        void onKeyDown(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ args)
        {
            key_args a;
            a.vk = static_cast<int>(args->Key);

            if (a.vk == 84) // 't'
            {
                BAJA_TRACE("emulating touch with the mouse, press ESC to exit this mode.");
                _emulatingTouch = true;
            }
            else if (a.vk == 27) // esc
            {
                BAJA_TRACE("touch emulation off");
                _emulatingTouch = false;
            }

            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onKey(a, context);
            });
        }

        void onPointerMoved(Platform::Object ^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^ args)
        {
            Windows::Foundation::Collections::IVector<PointerPoint^>^ points = args->GetIntermediatePoints(_topLevelDirectUIElement);

            for (int i = points->Size - 1; i >= 0; i--)
            {
                PointerPoint^ p = points->GetAt(i);
                pointer_args a(p->PointerId, pointer_event_type::move, toDeviceType(p->PointerDevice->PointerDeviceType));
                a.buttons = toPointerButtons(p->Properties);
                a.point.x = p->Position.X;
                a.point.y = p->Position.Y;
                a.pressure = p->Properties->Pressure;
                a.timestamp = p->Timestamp;
                a.keys = toKeyModifiers(args->KeyModifiers);
                a.eraser = p->Properties->IsEraser;
                a.barrelButtonPressed = p->Properties->IsBarrelButtonPressed;

                _shell.lock()->_inputSource.raise(
                    [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
                {
                    observer->onPointer(a, context);
                });
            }

            if (toDeviceType(points->GetAt(0)->PointerDevice->PointerDeviceType) == device_type::touch)
            {
                try
                {
                    _gestureRecognizer->ProcessMoveEvents(points);
                }
                catch (...) { }
            }
        }

        void onPointerPressed(Platform::Object ^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^ args)
        {
            PointerPoint^ p = args->GetCurrentPoint(_topLevelDirectUIElement);

            pointer_args a(p->PointerId, pointer_event_type::down, toDeviceType(p->PointerDevice->PointerDeviceType));
            a.buttons = toPointerButtons(p->Properties);
            a.point.x = p->Position.X;
            a.point.y = p->Position.Y;
            a.pressure = p->Properties->Pressure;
            a.timestamp = p->Timestamp;
            a.keys = toKeyModifiers(args->KeyModifiers);
            auto it = _activeTouchPoints.find(p->PointerId);
            if (it == _activeTouchPoints.end())
            {
                _activeTouchPoints[p->PointerId] = a;
            }

            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onPointer(a, context);
            });

            if (a.deviceType == device_type::touch)
            {
                try { _gestureRecognizer->ProcessDownEvent(p); } catch (...) {} 
            }
        }

        void onPointerReleased(Platform::Object ^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^ args)
        {
            PointerPoint^ p = args->GetCurrentPoint(_topLevelDirectUIElement);

            pointer_args a(p->PointerId, pointer_event_type::up, toDeviceType(p->PointerDevice->PointerDeviceType));
            a.buttons = toPointerButtons(p->Properties);
            a.point.x = p->Position.X;
            a.point.y = p->Position.Y;
            a.pressure = p->Properties->Pressure;
            a.timestamp = p->Timestamp;
            a.keys = toKeyModifiers(args->KeyModifiers);
            auto it = _activeTouchPoints.find(p->PointerId);
            if (it != _activeTouchPoints.end())
            {
                _activeTouchPoints.erase(it);
            }

            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onPointer(a, context);
            });

            if (a.deviceType == device_type::touch)	
            {
                try { _gestureRecognizer->ProcessUpEvent(p); } catch (...) {}

                if (!this->inertiaEnabled())
                {
                    _gestureRecognizer->CompleteGesture();
                }
            }
        }

        void onPointerWheelChanged(Platform::Object ^ sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs ^ args)
        {
            PointerPoint^ p = args->GetCurrentPoint(_topLevelDirectUIElement);

            pointer_args a(p->PointerId, pointer_event_type::wheel, toDeviceType(p->PointerDevice->PointerDeviceType));
            a.buttons = toPointerButtons(p->Properties);
            a.point.x = p->Position.X;
            a.point.y = p->Position.Y;
            a.wheelDelta = p->Properties->MouseWheelDelta;
            a.keys = toKeyModifiers(args->KeyModifiers);
            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onPointer(a, context);
            });

            _gestureRecognizer->ProcessMouseWheelEvent(
                p, 
                ((uint32)args->KeyModifiers & (uint32)Windows::System::VirtualKeyModifiers::Shift) != 0, 
                ((uint32)args->KeyModifiers & (uint32)Windows::System::VirtualKeyModifiers::Control) != 0
                );
        }

        void onManipulationStarted(GestureRecognizer^ sender, ManipulationStartedEventArgs^ args)
        {
            gesture_args a(gesture_event_type::start, toDeviceType(args->PointerDeviceType));
            a.cumulative.expansion = args->Cumulative.Expansion;
            a.cumulative.scale = args->Cumulative.Scale;
            a.cumulative.rotation = args->Cumulative.Rotation;
            a.cumulative.translation.x = args->Cumulative.Translation.X;
            a.cumulative.translation.y = args->Cumulative.Translation.Y;
            a.position.x = args->Position.X;
            a.position.y = args->Position.Y;
            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onGesture(a, context);
            });
        }

        void onManipulationCompleted(GestureRecognizer^ sender, ManipulationCompletedEventArgs^ args)
        {
            gesture_args a(gesture_event_type::complete, toDeviceType(args->PointerDeviceType));
            a.cumulative.expansion = args->Cumulative.Expansion;
            a.cumulative.scale = args->Cumulative.Scale;
            a.cumulative.rotation = args->Cumulative.Rotation;
            a.cumulative.translation.x = args->Cumulative.Translation.X;
            a.cumulative.translation.y = args->Cumulative.Translation.Y;
            a.position.x = args->Position.X;
            a.position.y = args->Position.Y;

            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onGesture(a, context);
            });
        }

        void onManipulationUpdated(GestureRecognizer^ sender, ManipulationUpdatedEventArgs^ args)
        {
            gesture_args a(gesture_event_type::update, toDeviceType(args->PointerDeviceType));
            a.delta.expansion = args->Delta.Expansion;
            a.delta.scale = args->Delta.Scale;
            a.delta.rotation = args->Delta.Rotation;
            a.delta.translation.x = args->Delta.Translation.X;
            a.delta.translation.y = args->Delta.Translation.Y;
            a.cumulative.expansion = args->Cumulative.Expansion;
            a.cumulative.scale = args->Cumulative.Scale;
            a.cumulative.rotation = args->Cumulative.Rotation;
            a.cumulative.translation.x = args->Cumulative.Translation.X;
            a.cumulative.translation.y = args->Cumulative.Translation.Y;
            a.position.x = args->Position.X;
            a.position.y = args->Position.Y;
            a.linearVelocity.x = args->Velocities.Linear.X;
            a.linearVelocity.y = args->Velocities.Linear.Y;

            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onGesture(a, context);
            });
        }

        void onHolding(GestureRecognizer^ sender, HoldingEventArgs^ args)
        {
            if (args->HoldingState == HoldingState::Started)
            {
                gesture_args a(gesture_event_type::hold, toDeviceType(args->PointerDeviceType));
                a.position = m::point2d(args->Position.X, args->Position.Y);
                _shell.lock()->_inputSource.raise(
                    [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
                {
                    observer->onGesture(a, context);
                });
            }
        }

        void onTapped(GestureRecognizer^ sender, TappedEventArgs^ args)
        {
            gesture_args a(args->TapCount == 1 ? gesture_event_type::tap : gesture_event_type::doubleTap, 
                toDeviceType(args->PointerDeviceType));
            a.position = m::point2d(args->Position.X, args->Position.Y);
            _shell.lock()->_inputSource.raise(
                [&a](const std::shared_ptr<iinput_observer>& observer, void* context)
            {
                observer->onGesture(a, context);
            });
        }

        device_type toDeviceType(const PointerDeviceType dt)
        {
            if ((_emulatingTouch && dt == PointerDeviceType::Mouse) || (dt == PointerDeviceType::Touch))
            {
                return device_type::touch;
            }
            else if (dt == PointerDeviceType::Mouse)
            {
                return device_type::mouse;
            }
            else if (dt == PointerDeviceType::Pen)
            {
                return device_type::pen;
            }
            else
            {
                BAJA_THROW(std::invalid_argument("DeviceType")); // ERROR:
            }

            return device_type::touch;
        }

        baja::app::orientation toOrientation(const DisplayOrientations o)
        {
            if (o == DisplayOrientations::Landscape)
            {
                return baja::app::orientation::landscape;
            }
            else if (o == DisplayOrientations::Portrait)
            {
                return baja::app::orientation::portrait;
            }
            else if (o == DisplayOrientations::LandscapeFlipped)
            {
                return baja::app::orientation::landscapeFlipped;
            }
            else if (o == DisplayOrientations::PortraitFlipped)
            {
                return baja::app::orientation::portraitFlipped;
            }
            else
            {
                BAJA_VERIFY(false);
                return baja::app::orientation::portrait;
            }
        }

        uint32 toPointerButtons(Windows::UI::Input::PointerPointProperties^ properties)
        {
            uint32 returnValue = 0x0;

            if (properties->IsLeftButtonPressed)
            {
                returnValue |= static_cast<uint32>(pointer_button::left);
            }

            if (properties->IsRightButtonPressed)
            {
                returnValue |= static_cast<uint32>(pointer_button::right);
            }

            if (properties->IsMiddleButtonPressed)
            {
                returnValue |= static_cast<uint32>(pointer_button::middle);
            }

            return returnValue;
        }

        key_modifiers toKeyModifiers(const VirtualKeyModifiers vkm)
        {
            uint32 i = static_cast<uint32>(vkm);
            uint32 j = static_cast<uint32>(key_modifiers::none);

            if (i & static_cast<uint32>(VirtualKeyModifiers::Control))
            {
                j |= static_cast<uint32>(key_modifiers::control);
            }
            if (i & static_cast<uint32>(VirtualKeyModifiers::Menu))
            {
                j |= static_cast<uint32>(key_modifiers::menu);
            }
            if (i & static_cast<uint32>(VirtualKeyModifiers::Windows))
            {
                j |= static_cast<uint32>(key_modifiers::windows);
            }
            if (i & static_cast<uint32>(VirtualKeyModifiers::Shift))
            {
                j |= static_cast<uint32>(key_modifiers::shift);
            }

            return static_cast<key_modifiers>(j);
        }

        void onSuspending(Platform::Object^ sender, SuspendingEventArgs^ args)
        {
            // TRACK: 210: implement suspend and resume pass the event down to the root
             SuspendingDeferral^ deferral = args->SuspendingOperation->GetDeferral();

             std::shared_ptr<isuspension_deferral> def = std::static_pointer_cast<isuspension_deferral>(
                 std::make_shared<suspension_deferral>(deferral));

             _app.lock()->onSuspend(def);
        }

        void onResuming(Platform::Object^ sender, Platform::Object^ args)
        {
            // TRACK: 210: implement suspend and resume pass the event down to the root
        }
    };

public:

    shell(Windows::UI::Xaml::Application^ shellApp, Windows::UI::Core::CoreWindow^ shellWindow, Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ topLevelDirectUIElement) 
        :
        _shellApp(shellApp),
        _shellWindow(shellWindow),
        _topLevelDirectUIElement(topLevelDirectUIElement)
        {
            _canvas = ref new Windows::UI::Xaml::Controls::Canvas();
            _topLevelDirectUIElement->Children->Append(_canvas);
        }

        // the shell class is just a wrapper on a winrt object (viewprovider) so it can provide
        // a "classic" c++ interface on such object
        static std::shared_ptr<shell> create(Windows::UI::Xaml::Application^ shellApp, Windows::UI::Core::CoreWindow^ shellWindow, Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ topLevelDirectUIElement)
        {
            std::shared_ptr<shell> returnValue = std::make_shared<shell>(shellApp, shellWindow, topLevelDirectUIElement);
            return returnValue;
        }

        // iapp_shell_base

        void setApp(const std::shared_ptr<iapp>& app)
        {
            // register ourselves as the shell service
            baja::services::set<shell>(std::static_pointer_cast<iapp_shell_service>(shared_from_this()));

            // if there was already an app connected to this shell, disconnect it 
            if (_app.lock())
            {
                _inputSource -= std::static_pointer_cast<iinput_observer>(_app.lock());
                _innerDirectUIShell = nullptr;
                _app.reset();
            }

            _app = app;

            _innerDirectUIShell = ref new inner_directui_shell(shared_from_this(), _app.lock(), _topLevelDirectUIElement);
            _innerDirectUIShell->initialize(_shellApp, _shellWindow.Get());
            
            // add the app as a default input observer
            _inputSource += std::static_pointer_cast<iinput_observer>(_app.lock());
        }
        
        // iinput_relay

        int32 addInputObserver(const std::shared_ptr<iinput_observer>& observer, void* context = nullptr)
        {
            return _inputSource.addObserver(observer, context);
        }

        void removeInputObserver(const std::shared_ptr<iinput_observer>& observer)
        {
            _inputSource -= observer;
        }

        void removeInputObserver(const int32 cookie)
        {
            _inputSource -= cookie;
        }

        // iinput_capture

        void setCapture(const std::shared_ptr<iinput_observer>& observer)
        {
            _inputSource.setCapture(observer);
        }

        void releaseCapture()
        {
            _inputSource.releaseCapture();
        }

        std::shared_ptr<iinput_observer> getCapture(void** context = nullptr)
        {
            return _inputSource.getCapture(context);
        }

        // iapp_shell_service

        baja::uint32 touchPointCount()
        {
            return _innerDirectUIShell->touchPointCount();
        }
    
        const std::map<uint32, pointer_args>& touchPoints()
        {
            return _innerDirectUIShell->touchPoints();
        }

        baja::app::orientation orientation()
        {
            return _innerDirectUIShell->orientation();
        }

        float32 logicalDpi()
        {
            return _innerDirectUIShell->logicalDpi();
        }

        void enableInertia(const float32 deceleration)
        {
            _innerDirectUIShell->enableInertia(deceleration);
        }

        void disableInertia()
        {
            _innerDirectUIShell->disableInertia();
        }

        bool inertiaEnabled() { return _innerDirectUIShell->inertiaEnabled(); }

        const m::point2d& windowSize()
        {
            return _innerDirectUIShell->windowSize();
        }

        void clearTile()
        {
            _innerDirectUIShell->clearTile();
        }

        void updateTile(const std::wstring& fileName)
        {
            _innerDirectUIShell->updateTile(fileName);
        }

        // iloopless_app_shell

        void render()
        {
            _app.lock()->onRender();
        }
        
    private:
        observable_source<iinput_observer> _inputSource;
        observable_source<irender_observer> _renderSource;
        std::weak_ptr<iapp> _app;
        inner_directui_shell^ _innerDirectUIShell;
        Windows::UI::Xaml::Controls::Canvas^ _canvas;
        Windows::UI::Xaml::Application^ _shellApp;
        Platform::Agile<Windows::UI::Core::CoreWindow> _shellWindow;
        Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ _topLevelDirectUIElement;
    };

}}} // namespace baja::app::directui

namespace baja { namespace app {

inline std::shared_ptr<iloopless_app_shell> createShell(
    Windows::UI::Xaml::Application^ shellApp, 
    Windows::UI::Core::CoreWindow^ shellWindow, 
    Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ topLevelDirectUIElement)
{
    std::shared_ptr<baja::app::directui::shell> returnValue = baja::app::directui::shell::create(shellApp, shellWindow, topLevelDirectUIElement);
    return std::dynamic_pointer_cast<iloopless_app_shell>(returnValue);
}

}} // namespace baja::app